#include "stdafx.h"

#include "app/test_shaders.h"
#include "core/kernel.h"
#include "input/keyboard.h"
#include "util/profiler.h"

#include "video/program.h"
#include "video/fbo.h"

#include "scene/fsquad.h"

#include "util/make_unique.h"

namespace natrium {
	namespace app {
		TestShaders::TestShaders(): 
			Application("TEST: Shaders", core::Task::SINGLETHREADED_REPEATING)
		{
			mChan.add<input::Keyboard::KeyPressed>(this);
			mChan.add<video::WindowCreated>(this);
		}

		TestShaders::~TestShaders() {
		}

		bool TestShaders::init() {
			System::init();

			mShaderProgram.compileFile("shaders/deferred.vert", video::VERTEX);
			mShaderProgram.compileFile("shaders/deferred.frag", video::FRAGMENT);
			mShaderProgram.link();
			mShaderProgram.use();

			mCube = std::make_unique<scene::Cube>(1.0f);
			mMesh = std::make_unique<scene::Mesh>("models/bunny.obj", true, false, false);
			mFSQuad = std::make_unique<scene::FSQuad>();
		
			GLuint programHandle = mShaderProgram.getHandle();
			mProgramPass1 = glGetSubroutineIndex(programHandle, GL_FRAGMENT_SHADER, "pass1");
			mProgramPass2 = glGetSubroutineIndex(programHandle, GL_FRAGMENT_SHADER, "pass2");

			mShaderProgram.set("PositionTexture", 0);
			mShaderProgram.set("NormalTexture", 1);
			mShaderProgram.set("ColorTexture", 2);
			mShaderProgram.set("Light.Intensity", glm::vec3(1.0f, 1.0f, 1.0f));

			mAngle = 0.0f;

			return true;
		}

		void TestShaders::update() {
			pass1();
			pass2();

			mAngle += 0.001f;
		}

		void TestShaders::pass1() {
			glBindFramebuffer(GL_FRAMEBUFFER, mFBO->getHandle()); //bind the FBO to the framebuffer
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //clear old data
			glEnable(GL_DEPTH_TEST); //enable depth testing
			glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &mProgramPass1); //set the current fragment shader subroutine to pass 1

			//automatically move the camera in a circle orbiting (0, 0, 0)
			mView = glm::lookAt(
				glm::vec3(7.0f * cos(mAngle), 4.0f, 7.0f * sin(mAngle)),
				glm::vec3(0.0f, 0.0f, 0.0f),
				glm::vec3(0.0f, 1.0f, 0.0f)
			);

			//light position is constant at (0.0f, 0.0f, 0.0f)
			mShaderProgram.set("Light.Position", glm::vec4(0.0f, 4.0f, 0.0f, 1.0f));
			mShaderProgram.set("Material.Kd", 0.9f, 0.9f, 0.9f); //Diffuse reflective material property

			mModel = glm::mat4(1.0f);
			mModel *= glm::translate(glm::vec3(0.0f, 0.0f, 0.0f)); //move the model to a position
			mModel *= glm::rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f)); //rotate 90 degrees(?radians?) around the X axis

			glm::mat4 modelView = mView * mModel;
			mShaderProgram.set("ModelViewMatrix", modelView);
			mShaderProgram.set("NormalMatrix", 
				glm::mat3(
					glm::vec3(modelView[0]),
					glm::vec3(modelView[1]),
					glm::vec3(modelView[2])
				)
			);
			mShaderProgram.set("ModelViewProjection", mProjection * modelView);

			mMesh->render();
			//mCube->render();

			glFinish(); //what does this do?
		}

		void TestShaders::pass2() {
			glBindFramebuffer(GL_FRAMEBUFFER, 0); //this pass will render to the default framebuffer (straight to the screen)
			glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &mProgramPass2); //set the current fragment shader subroutine to pass 2

			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //clear the previous frame
			glDisable(GL_DEPTH_TEST); //depth testing was already done in the previous pass

			//set identity matrices for all transforms
			mShaderProgram.set("ModelViewMatrix", glm::mat4(1.0f));
			mShaderProgram.set("NormalMatrix", glm::mat3(1.0f));
			mShaderProgram.set("ModelViewProjection", glm::mat4(1.0f));

			glBindVertexArray(mFSQuad->getHandle());
			glDrawArrays(GL_TRIANGLES, 0, 6);
		}

		void TestShaders::shutdown() {
			System::shutdown();
		}

		void TestShaders::operator()(const video::WindowCreated& wc) {
			auto width = wc.mWindow->getWidth();
			auto height = wc.mWindow->getHeight();

			mFBO = std::make_unique<video::FrameBufferObject>(width, height);

			//projection could remain constant
			mProjection = glm::perspective(60.0f, wc.mWindow->getWindowAspectRatio(), 0.3f, 100.0f);

			//set the viewport to the eniter window
			glViewport(0, 0, width, height);
		}

		void TestShaders::operator()(const input::Keyboard::KeyPressed& kp) {
			switch (kp.mKey) {
			case GLFW_KEY_ESC:
				mChan.broadcast(core::Kernel::StopEvent());
				break;

			case GLFW_KEY_F1:
				mShaderProgram.listActiveAttribs();
				break;

			case GLFW_KEY_F2:
				mShaderProgram.listActiveUniforms();
				break;
			}
		}
	}
}